#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define QQ_CNT      1
#define QQ_NAME     "qq"
#define BEEP_OFF    0
#define BEEP_ON     1

struct qq_dev
{
    dev_t devid;                   /* 设备号 */
    struct cdev cdev;              /* cdev */
    struct class *class;           /* 类 */
    struct device *device;         /* 设备 */
    int major;                     /* 主设备号 */
    int minor;                     /* 次设备号 */
    struct device_node *nd;        /* 设备节点 */
    int beep_gpio;                 /* 打开关闭gpio */  
};

static struct qq_dev qq;

void beep_switch(u8 cmd)
{
    if(cmd == BEEP_ON)
    {
        gpio_set_value(qq.beep_gpio, 0);
    }
    else if(cmd == BEEP_OFF)
    {
        gpio_set_value(qq.beep_gpio, 1);
    }
}
static int qq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &qq;
    return 0;
}

static ssize_t qq_read(struct file *filp, char __user *buf, size_t count, loff_t *offt)
{
    return 0;
}

static ssize_t qq_write(struct file *filp, const char __user *buf, size_t count, loff_t *offt)
{
    int ret=0;
    unsigned char databuf[1];
    unsigned char beepsta;
    ret=copy_from_user(databuf,buf,count);
    if(ret<0);
    {
        printk("kernel write fail\r\n");
        return -EFAULT;
    }

    beepsta=databuf[0];
    if(beepsta==BEEP_ON)
    {
        beep_switch(BEEP_ON);
    }else if (beepsta==BEEP_OFF)
    {
        beep_switch(BEEP_OFF);
    }
    
    return 0;
}

static int qq_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static struct file_operations qq_fops =
{
    .owner = THIS_MODULE,
    .open = qq_open,
    .read = qq_read,
    .write = qq_write,
    .release = qq_release,
};

static int qq_probe(struct platform_device *pdev)
{
    alloc_chrdev_region(&qq.devid, 0, QQ_CNT, QQ_NAME);
    qq.major = MAJOR(qq.devid);
    qq.minor = MINOR(qq.devid);
    cdev_init(&qq.cdev, &qq_fops);
    cdev_add(&qq.cdev, qq.devid, QQ_CNT);
    qq.class = class_create(THIS_MODULE, QQ_NAME);
    qq.device = device_create(qq.class, NULL, qq.devid, NULL, QQ_NAME);
    qq.nd = of_find_node_by_path("/dtsplatformbeep");
    qq.beep_gpio = of_get_named_gpio(qq.nd, "beep-gpios", 0);
    gpio_request(qq.beep_gpio, "beep");
    gpio_direction_output(qq.beep_gpio, 1);
    return 0;
}


static int qq_remove(struct platform_device *pdev)
{
    gpio_set_value(qq.beep_gpio, 1);
    gpio_free(qq.beep_gpio);
    device_destroy(qq.class, qq.devid);
    class_destroy(qq.class);
    unregister_chrdev_region(qq.devid, QQ_CNT);
    cdev_del(&qq.cdev);
    return 0;
}

static const struct of_device_id qq_of_match[] =
{
    {.compatible = "xby-beep"},
    { /* Sentinel */ }
};

MODULE_DEVICE_TABLE(of, qq_of_match);

static struct platform_driver qq_driver =
{
    .probe = qq_probe,
    .remove = qq_remove,
    .driver = 
    {
        .name = "imx6ull-beep",
        .of_match_table = qq_of_match,
    },
};

static int __init qq_init(void)
{
    return platform_driver_register(&qq_driver);
}

static void __exit qq_exit(void)
{
    platform_driver_unregister(&qq_driver);
}

module_init(qq_init);
module_exit(qq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xby");